/*a small file client
Usage: suppose client is running on sd1.encs.concordia.ca and server is running on sd2.encs.concordia.ca
.Also suppose there is a file called test.txt on the server.
In the client,issuse "client sd2.encs.concordia.ca test.txt size" and you can get the size of the file.
In the client,issuse "client sd2.encs.concordia.ca test.txt time" and you can get creation time of the file
*/

///////////////////////////////////////////
// Authors : Mathieu Robitaille - 6396682
//           Sacha Sukhdeo - 6054412
// Class : COMP445
//         Assignment 2
///////////////////////////////////////////

#include "client.h"
#include <iostream>
#include <stdio.h>
#include <fstream>
#include <time.h>

#define REQUEST_PORT_CLI_IN 5000
#define REQUEST_PORT_CLI_OUT 7000

TcpClient::TcpClient() {
    OutputDebugStringW(L"\n\n TcpClient::TcpClient  \n\n");
    memset(&wsadata, 0, sizeof(wsadata));
    memset(&m_username, 0, sizeof(m_username));
    memset(&m_hostname, 0, sizeof(m_hostname));
	memset(&m_req, 0, sizeof(m_req));
    memset(&m_smsg, 0, sizeof(m_smsg));
    memset(&m_rmsg, 0, sizeof(m_rmsg));
    mp_serverhost = NULL;

    DWORD unsize = sizeof(m_username);
    if (!GetUserName(m_username, &unsize))
        err_sys("Error: could not get user name from OS.");
	memcpy(m_req.username, m_username, UNLEN);
    int err = WSAStartup(MAKEWORD(2,2), &wsadata);
    m_wsaInitialized = err == 0;
    if (!m_wsaInitialized)
        err_sys("Error: Could not initialize WSA. Error %d", err);
}

void TcpClient::run(int argc,char * argv[])
{
	const std::string QUIT = "quit";
    //OutputDebugStringW(L"\n\n TcpClient::run - start  \n\n");
    if(gethostname(m_hostname,HOSTNAME_LENGTH) != 0) //get the hostname
		err_sys("can not get the host name,program exit");
    std::cout << "ftp_tcp starting on host " << m_hostname << " by " << m_username << std::endl;
    strcpy_s(m_req.hostname, m_hostname);

    std::string servername;
    do {
        std::cout << "Type the name of the FTP server : ";
        std::getline(std::cin, servername);
        if (servername != QUIT) {
            if (ConnectToServer(servername) < 0)
                servername.clear();
            else if (OpenSocket(mp_serverhost) == 0) {
                RequestServer(mp_serverhost);
                CloseSocket();
            }
        }
    } while (servername != QUIT);
}

int TcpClient::ConnectToServer(const std::string& servername) {
    hostent *serverhost = NULL;
    serverhost = gethostbyname(servername.c_str());
    if (serverhost == NULL) {
        std::cerr << "Server named '" << servername << "' unreachable." << std::endl;
        return -1;
    }
    mp_serverhost = serverhost;
    return 0;
}

int TcpClient::OpenSocket(hostent* serverhost) {
    if ((m_serverSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET)
        err_sys("Error: Unable to create server socket. Code %i", WSAGetLastError());

    memset(&m_servAddr, 0, sizeof(m_servAddr));
    m_servAddr.sin_family = AF_INET;
    m_servAddr.sin_addr.S_un.S_addr = *((unsigned long *) serverhost->h_addr_list[0]);
    m_servAddr.sin_port = htons(REQUEST_PORT_CLI_OUT);
    //if (bind(m_serverSocket, (SOCKADDR*)&m_servAddr, sizeof(m_servAddr)) == SOCKET_ERROR)
    //    err_sys("Error: Unable to bind the socket to the address. Code %i", WSAGetLastError());
    if (connect(m_serverSocket, (SOCKADDR*) &m_servAddr, sizeof(m_servAddr)) == SOCKET_ERROR)
        err_sys("Error: Unable to connect to server. Code %i", WSAGetLastError());

    if ((m_clientSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET)
        err_sys("Error: Unable to create client socket. Code %i", WSAGetLastError());
    m_clientAddr.sin_family = AF_INET;
    m_clientAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    m_clientAddr.sin_port = htons(REQUEST_PORT_CLI_IN);
    if (bind(m_clientSocket, (SOCKADDR*)&m_clientAddr, sizeof(m_clientAddr)) == SOCKET_ERROR)
        err_sys("Error: Unable to bind client socket. Code %i", WSAGetLastError());

	// Do 3-way handshake here
		ThreeWayHandShake();

    return 0;
}

void TcpClient::ThreeWayHandShake()
{

	//---------------------------------------------------------------------------------------------------------------Sacha.start
	isHandShaking = true;


	fd_set fdset;
	FD_ZERO(&fdset);
	FD_SET(m_clientSocket, &fdset);

	while(isHandShaking) {
	    srand((unsigned)time(NULL));   
	    localSeqNb = rand() % 256; // generate random number from 0 and 255
	    Msg msg;
        memset(&msg, 0, sizeof(msg));
	    HandShakeMsg handShakeMsg;
        memset(&handShakeMsg, 0, sizeof(handShakeMsg));
	    handShakeMsg.Ack = localSeqNb;
	    msg.type= HANDSHAKE;
	    handShakeMsg.handShakeSTEP = FIRST_STEP_SHAKE;
	    memcpy(msg.buffer, &handShakeMsg, msg.length = sizeof(handShakeMsg));
	    msg_send(m_serverSocket, &msg, (SOCKADDR*) &m_servAddr, sizeof(m_servAddr));

		if (WaitForSocket(fdset, &TIMEOUT)) {
			Msg rmsg; 
			msg_recv(m_clientSocket, &rmsg);
            HandShakeMsg &handShakeMsgRec = *(HandShakeMsg*)rmsg.buffer;

			if(handShakeMsgRec.ReturnAck == localSeqNb) {
				std::cout << "SECOND_STEP_SHAKE Received" << std::endl;

				HandShakeMsg handShakeMsgSecond;
				memset(&handShakeMsgSecond, 0 , sizeof(handShakeMsgSecond));
				handShakeMsgSecond.ReturnAck = handShakeMsgRec.Ack;
				handShakeMsgSecond.handShakeSTEP = THIRD_STEP_SHAKE;
				handShakeMsgSecond.Ack = NULL;
				remoteSeqNb = handShakeMsgRec.Ack;

                Msg  msgSecond;
                memset(&msgSecond, 0, sizeof(msgSecond));
                msgSecond.type= HANDSHAKE;
                msgSecond.length = sizeof(handShakeMsgSecond);
                memcpy(msgSecond.buffer, &handShakeMsgSecond, sizeof(handShakeMsgSecond));

				msg_send(m_serverSocket, &msgSecond, (SOCKADDR*) &m_servAddr, sizeof(m_servAddr));
				isHandShaking = false;	
			}
		}
		else
		{
			std::cout << "SECOND_STEP_SHAKE   never Received" << std::endl;
		}
	}
//---------------------------------------------------------------------------------------------------------------Sacha.end

}

void TcpClient::RequestServer(hostent* serverhost) {
    std::string filename;
    std::cout << "Enter the file name to be transfered : ";
    std::cin >> filename;
    strcpy_s(m_req.filename, filename.c_str());

    std::string command;

    while (command.empty())
    {
        std::cout << "Enter the command (get, put, list) : ";
        std::cin >> command;

        if (command == "get")
            m_smsg.type = REQ_GET;
        else if (command == "put")
            m_smsg.type = REQ_PUT;
        else if (command == "list")
            m_smsg.type = REQ_LIST;
		else if (command == "delete")
            m_smsg.type = REQ_DEL;
        else {
            std::cerr << "Invalid command." << std::endl;
            command.clear();
        }
    }

    m_smsg.length = min(sizeof(m_req), sizeof(m_smsg.buffer));
    memcpy(m_smsg.buffer, &m_req, m_smsg.length);
    LogInfo li;
    memset(&li, 0, sizeof(li));
	SendPacket(m_serverSocket, m_clientSocket, m_smsg, (SOCKADDR*) &m_servAddr, sizeof(m_servAddr), li, false);

    switch (m_smsg.type)
    {
    case REQ_PUT:
		SendFile(m_req.filename, m_serverSocket, m_clientSocket, serverhost->h_name, (SOCKADDR*) &m_servAddr, sizeof(m_servAddr));
		break;
    case REQ_GET:
		ReceiveFile(m_req.filename, m_serverSocket, m_clientSocket, (SOCKADDR*)&m_servAddr, serverhost->h_name);
		break;
    case REQ_LIST:
        ReceiveFileList(m_serverSocket, m_clientSocket, (LPSOCKADDR)&m_servAddr);
        break;
    case REQ_DEL:
        {
            Msg m;
            m = ReceivePacket(m_clientSocket, m_serverSocket, (LPSOCKADDR)&m_servAddr, li);
            if (m.type == MSG_ERR) {
                char *c = new char[m.length];
                memset(c, 0, m.length);
                memcpy(c, m.buffer, m.length);
                std::cerr << c << std::endl;
                delete [] c;
            }
        }
    }

    std::string s;
    std::getline(std::cin, s);
}

void TcpClient::CloseSocket() {
    closesocket(m_serverSocket);
    closesocket(m_clientSocket);
    m_serverSocket = INVALID_SOCKET;
    m_clientSocket = INVALID_SOCKET;
}

TcpClient::~TcpClient()
{
	if (m_wsaInitialized)
	    WSACleanup(); // Only clean up on successful call
}

int main(int argc, char *argv[])
{
	TcpClient tc;
	tc.run(argc,argv);
	return 0;
}
